/* bzflag
 * Copyright (c) 1993-2020 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/* common headers */
#include "common.h"

/* interface headers */
#include "EvdevJoystick.h"

#ifdef HAVE_LINUX_INPUT_H

/* system headers */
#include <vector>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

/* implementation headers */
#include "ErrorHandler.h"
#include "bzfio.h"

static inline int test_bit (int nr, const volatile void *addr)
{
    return 1 & (((const volatile __u32 *) addr)[nr >> 5] >> (nr & 31));
}

bool         EvdevJoystick::isEvdevAvailable()
{
    /* Test whether this driver should be used without actually
     * loading it. Will return false if no event devices can be
     * located, or if it has been specifically disabled by setting
     * the environment variable BZFLAG_ENABLE_EVDEV=0
     */

    char *envvar = getenv("BZFLAG_ENABLE_EVDEV");
    if (envvar)
        return atoi(envvar) != 0;

    std::map<std::string,EvdevJoystickInfo> joysticks;
    scanForJoysticks(joysticks);
    return !joysticks.empty();
}


EvdevJoystick::EvdevJoystick()
{
    joystickfd = 0;
    currentJoystick = NULL;
#if defined(HAVE_FF_EFFECT_DIRECTIONAL) || defined(HAVE_FF_EFFECT_RUMBLE)
    ff_rumble = new struct ff_effect;
#else
    ff_rumble = NULL;
#endif
    scanForJoysticks(joysticks);
}

EvdevJoystick::~EvdevJoystick()
{
    initJoystick("");
    delete ff_rumble;
}

void         EvdevJoystick::scanForJoysticks(std::map<std::string,
        EvdevJoystickInfo> &joysticks)
{
    joysticks.clear();

    const std::string inputdirName = "/dev/input";
    DIR* inputdir = opendir(inputdirName.c_str());
    if (!inputdir)
        return;

    struct dirent *dent;
    while ((dent = readdir(inputdir)))
    {
        EvdevJoystickInfo info;

        /* Does it look like an event device? */
        if (strncmp(dent->d_name, "event", 5))
            continue;

        /* Can we open it for r/w? */
        info.filename = inputdirName + "/" + dent->d_name;
        int fd = open(info.filename.c_str(), O_RDWR);
        /* if we can't open read/write, try just read...if it's not ff it'll work anyhow */
        if (!fd)
        {
            fd = open(info.filename.c_str(), O_RDONLY);
            if (fd) logDebugMessage(4,"Opened event device %s as read-only.\n", info.filename.c_str());
        }
        else
            logDebugMessage(4,"Opened event device %s as read-write.\n", info.filename.c_str());
        /* no good, can't open it */
        if (!fd)
        {
            logDebugMessage(4,"Can't open event device %s.  Check permissions.\n", info.filename.c_str());
            continue;
        }

        /* Does it look like a joystick? */
        if (!(collectJoystickBits(fd, info) && isJoystick(info)))
        {
            logDebugMessage(4,"Device %s doesn't seem to be a joystick.  Skipping.\n", info.filename.c_str());
            close(fd);
            continue;
        }

        /* Can we get its name? */
        char jsname[128];
        if (ioctl(fd, EVIOCGNAME(sizeof(jsname)-1), jsname) < 0)
        {
            close(fd);
            continue;
        }
        jsname[sizeof(jsname)-1] = '\0';

        close(fd);

        /* Yay, add it to our map.
         *
         * FIXME: we can't handle multiple joysticks with the same name yet.
         *  This could be fixed by disambiguating jsname if it already
         *  exists in 'joysticks', but the user would still have a hard
         *  time knowing which device to pick.
         */
        joysticks[jsname] = info;
    }

    closedir(inputdir);
}

bool            EvdevJoystick::collectJoystickBits(int fd, struct EvdevJoystickInfo &info)
{
    /* Collect all the bitfields we're interested in from an event device
     * at the given file descriptor.
     */
    if (ioctl(fd, EVIOCGBIT(0, sizeof(info.evbit)), info.evbit) < 0)
        return false;
    if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(info.keybit)), info.keybit) < 0)
        return false;
    if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(info.absbit)), info.absbit) < 0)
        return false;
#if defined(HAVE_FF_EFFECT_DIRECTIONAL) || defined(HAVE_FF_EFFECT_RUMBLE)
    if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(info.ffbit)), info.ffbit) < 0)
        return false;
#endif

    /* Collect information about our absolute axes */
    int axis;
    for (axis=0; axis<8; axis++)
    {
        if (ioctl(fd, EVIOCGABS(axis + ABS_X), &info.axis_info[axis]) < 0)
            return false;
    }

    return true;
}

bool            EvdevJoystick::isJoystick(struct EvdevJoystickInfo &info)
{
    /* Look at the capability bitfields in the given EvdevJoystickInfo, and
     * decide whether the device is indeed a joystick. This uses the same criteria
     * that SDL does- it at least needs X and Y axes, and one joystick-like button.
     */
    if (!test_bit(EV_KEY, info.evbit))
        return false;
    if (!(test_bit(BTN_TRIGGER, info.keybit) ||
            test_bit(BTN_A, info.keybit) ||
            test_bit(BTN_1, info.keybit)))
        return false;

    if (!test_bit(EV_ABS, info.evbit))
        return false;
    if (!(test_bit(ABS_X, info.absbit) &&
            test_bit(ABS_Y, info.absbit)))
        return false;

    return true;
}

void            EvdevJoystick::initJoystick(const char* joystickName)
{
    /* Close the previous joystick */
    ffResetEffect();
    if (joystickfd > 0)
        close(joystickfd);
    currentJoystick = NULL;
    joystickfd = 0;

    if (!strcasecmp(joystickName, "off") || !strcmp(joystickName, ""))
    {
        /* No joystick configured, we're done */
        return;
    }

    std::map<std::string,EvdevJoystickInfo>::iterator iter;
    iter = joysticks.find(joystickName);
    if (iter == joysticks.end())
    {
        printError("The selected joystick no longer exists.");
        return;
    }

    /* Looks like we might have a valid joystick, try to open it */
    EvdevJoystickInfo *info = &iter->second;
    joystickfd = open(info->filename.c_str(), O_RDWR | O_NONBLOCK);
    if (joystickfd > 0)
    {
        /* Yay, it worked */
        currentJoystick = info;
        currentJoystick->readonly = false;
    }
    else
    {
        joystickfd = open(info->filename.c_str(), O_RDONLY | O_NONBLOCK);
        if (joystickfd > 0)
        {
            /* Got it in read-only */
            currentJoystick = info;
            currentJoystick->readonly = true;
            printError("No write access to joystick device, force feedback disabled.");
        }
        else
            printError("Error opening the selected joystick.");
    }

    useaxis[0] = ABS_X;
    useaxis[1] = ABS_Y;
    buttons = 0;
    //for some reason, I can't put this in getJoyHats
    hataxes.assign(4 * 2, 0);
}

bool            EvdevJoystick::joystick() const
{
    return currentJoystick != NULL;
}

void            EvdevJoystick::poll()
{
    /* Read as many input events as are available, and update our current state
     */
    struct input_event ev;
    while (read(joystickfd, &ev, sizeof(ev)) > 0)
    {
        switch (ev.type)
        {

        case EV_ABS:
            if (ev.code > ABS_WHEEL)
            {
                if (ABS_HAT0X <= ev.code && ev.code <= ABS_HAT3Y)
                    hataxes[ev.code - ABS_HAT0X] = ev.value;
                break;
            }
            currentJoystick->axis_info[ev.code - ABS_X].value = ev.value;
            break;

        case EV_KEY:
            setButton(mapButton(ev.code), ev.value);
            break;

        }
    }
}

int          EvdevJoystick::mapButton(int bit_num)
{
    /* Given an evdev button number, map it back to a small integer that most
     * people would consider the button's actual number. This also ensures
     * that we can fit all buttons in "buttons" as long as the number of buttons
     * is less than the architecture's word size ;)
     *
     * We just scan through the joystick's keybits, counting how many
     * set bits we encounter before this one. If the indicated bit isn't
     * set in keybits, this is a bad event and we return -1.
     * If this linear scan becomes a noticeable performance drain, this could
     * easily be precomputed and stored in an std:map.
     */
    int i;
    int button_num = 0;
    const int total_bits = sizeof(currentJoystick->keybit)*sizeof(unsigned long)*8;

    for (i=0; i<total_bits; i++)
    {
        if (i == bit_num)
            return button_num;
        if (test_bit(i, currentJoystick->keybit))
            button_num++;
    }
    return -1;
}

void            EvdevJoystick::setButton(int button_num, int state)
{

    if (button_num >= 0)
    {
        int mask = 1<<button_num;
        if (state)
            buttons |= mask;
        else
            buttons &= ~mask;
    }
}

void            EvdevJoystick::getJoy(int& x, int& y)
{
    if (currentJoystick)
    {
        poll();

        int axes[9];
        int axis;
        int value;
        for (axis=0; axis<9; axis++)
        {
            if (!(test_bit(ABS_X + axis, currentJoystick->absbit)))
                continue;

            /* Each axis gets scaled from evdev's reported minimum
             * and maximum into bzflag's [-1000, 1000] range.
             */
            value = currentJoystick->axis_info[axis].value;
            value -= currentJoystick->axis_info[axis].minimum;
            value = value * 2000 / (currentJoystick->axis_info[axis].maximum -
                                    currentJoystick->axis_info[axis].minimum);
            value -= 1000;

            /* No cheating by modifying joystick drivers, or using some that rate
             * their maximum and minimum conservatively like the input spec allows.
             */
            if (value < -1000) value = -1000;
            if (value >  1000) value =  1000;

            /* All the cool kids are doing it... */
            value = (value * abs(value)) / 1000;

            axes[axis] = value;
        }
        x = axes[useaxis[0]];
        y = axes[useaxis[1]];
    }
    else
        x = y = 0;
}

int          EvdevJoystick::getNumHats()
{
    numHats = 0;
    if (currentJoystick)
    {
        for (int i = 0; i < 4; ++i)
        {
            if (test_bit(ABS_HAT0X + i * 2, currentJoystick->absbit))
                numHats++;
        }
    }
    //for some reason, I can't move this from initJoystick
    //hataxes.assign(numHats * 2, 0);
    return numHats;
}

void            EvdevJoystick::getJoyHat(int hat, float &hatX, float &hatY)
{
    hatX = hatY = 0;
    if (currentJoystick)
    {
        if (hat < 4)
        {
            poll();
            hatX = hataxes[hat * 2];
            hatY = hataxes[hat * 2 + 1];
        }
    }
}

unsigned long       EvdevJoystick::getJoyButtons()
{
    if (currentJoystick)
    {
        poll();
        return buttons;
    }
    else
        return 0;
}

void            EvdevJoystick::getJoyDevices(std::vector<std::string>
        &list) const
{
    std::map<std::string,EvdevJoystickInfo>::const_iterator i;
    for (i = joysticks.begin(); i != joysticks.end(); ++i)
        list.push_back(i->first);
}

static const std::string anames[9] = { "X", "Y", "Z", "Rx", "Ry", "Rz", "Throttle", "Rudder", "Wheel" };

void        EvdevJoystick::getJoyDeviceAxes(std::vector<std::string>
        &list) const
{
    list.clear();

    if (!currentJoystick)
        return;

    for (int i = 0; i < 9; ++i)
        if (test_bit(ABS_X + i, currentJoystick->absbit))
            list.push_back(anames[i]);
}

void            EvdevJoystick::setXAxis(const std::string &axis)
{
    for (int i = 0; i < 9; ++i)
        if (anames[i] == axis)
            useaxis[0] = ABS_X + i;
}

void            EvdevJoystick::setYAxis(const std::string &axis)
{
    for (int i = 0; i < 9; ++i)
        if (anames[i] == axis)
            useaxis[1] = ABS_X + i;
}

bool            EvdevJoystick::ffHasRumble() const
{
#ifdef HAVE_FF_EFFECT_RUMBLE
    if (!currentJoystick)
        return false;
    else
        return test_bit(EV_FF, currentJoystick->evbit) &&
               test_bit(FF_RUMBLE, currentJoystick->ffbit) &&
               !currentJoystick->readonly;
#else
    return false;
#endif
}

#if (defined HAVE_FF_EFFECT_DIRECTIONAL || defined HAVE_FF_EFFECT_RUMBLE)
void EvdevJoystick::writeJoystick(int count)
{
    struct input_event event;
    ssize_t     written_byte;

    event.type   = EV_FF;
    event.code   = ff_rumble->id;
    event.value  = count;
    written_byte = write(joystickfd, &event, sizeof(event));
    if (written_byte != sizeof(event))
        printError("Unable to write on joystick");
}
#else
void EvdevJoystick::writeJoystick(int)
{
}
#endif

void            EvdevJoystick::ffResetEffect()
{
#if (defined HAVE_FF_EFFECT_DIRECTIONAL || defined HAVE_FF_EFFECT_RUMBLE)
    /* Erase old effects before closing a device,
     * if we had any, then initialize the ff_rumble struct.
     */
    if ((ffHasRumble() || ffHasDirectional()) && ff_rumble->id != -1)
    {

        /* Stop the effect first */
        writeJoystick(0);

        /* Erase the downloaded effect */
        ioctl(joystickfd, EVIOCRMFF, ff_rumble->id);
    }

    /* Reinit the ff_rumble struct. It starts out with
     * an id of -1, prompting the driver to assign us one.
     * Once that happens, we stick with the same effect slot
     * as long as we have the device open.
     */
    memset(ff_rumble, 0, sizeof(*ff_rumble));
    ff_rumble->id = -1;
#endif
}

#ifdef HAVE_FF_EFFECT_RUMBLE
void            EvdevJoystick::ffRumble(int count,
                                        float delay, float duration,
                                        float strong_motor,
                                        float weak_motor)
{
    if (!ffHasRumble())
        return;

    /* Stop the previous effect we were playing, if any */
    if (ff_rumble->id != -1)
        writeJoystick(0);

    if (count > 0)
    {
        /* Download an updated effect */
        ff_rumble->type = FF_RUMBLE;
        ff_rumble->u.rumble.strong_magnitude = (int) (0xFFFF * strong_motor + 0.5);
        ff_rumble->u.rumble.weak_magnitude = (int) (0xFFFF * weak_motor + 0.5);
        ff_rumble->replay.length = (int) (duration * 1000 + 0.5);
        ff_rumble->replay.delay = (int) (delay * 1000 + 0.5);
        ioctl(joystickfd, EVIOCSFF, ff_rumble);

        /* Play it the indicated number of times */
        writeJoystick(count);
    }
}
#else
void EvdevJoystick::ffRumble(int, float, float, float, float)
{
}
#endif

bool EvdevJoystick::ffHasDirectional() const
{
#ifdef HAVE_FF_EFFECT_DIRECTIONAL
    if (!currentJoystick)
        return false;
    else
        return test_bit(EV_FF, currentJoystick->evbit) &&
               test_bit(FF_PERIODIC, currentJoystick->ffbit) &&
               test_bit(FF_CONSTANT, currentJoystick->ffbit) &&
               !currentJoystick->readonly;
#else
    return false;
#endif
}

#ifdef HAVE_FF_EFFECT_DIRECTIONAL
void EvdevJoystick::ffDirectionalConstant(int count, float delay, float duration,
        float x_direction, float y_direction,
        float strength)
{
    if (!ffHasDirectional())
        return;

    /* whenever we switch effect types we must reset the effect
     * this could be avoided by tracking the slot numbers and only
     * using one for each type.
     * When we don't switch types we still must stop the previous
     * effect we were playing, if any.
     */
    if (ff_rumble->type != FF_CONSTANT)
        ffResetEffect();
    else if (ff_rumble->id != -1)
        writeJoystick(0);

    if (count > 0)
    {
        /* Download an updated effect */
        ff_rumble->type = FF_CONSTANT;
        ff_rumble->u.constant.level = (int) (0x7FFF * strength + 0.5f);
        ff_rumble->direction = (int) (0xFFFF * (1.0 / (2.0 * M_PI)) *
                                      atan2(x_direction, -y_direction) + 0.5);
        ff_rumble->u.constant.envelope.attack_length = FF_NOMINAL_MIN;
        ff_rumble->u.constant.envelope.fade_length = FF_NOMINAL_MIN;
        ff_rumble->u.constant.envelope.attack_level = ff_rumble->u.constant.level;
        ff_rumble->u.constant.envelope.fade_level = ff_rumble->u.constant.level;
        ff_rumble->replay.length = (int) (duration * 1000 + 0.5f);
        ff_rumble->replay.delay = (int) (delay * 1000 + 0.5f);
        if (ioctl(joystickfd, EVIOCSFF, ff_rumble) == -1)
            printError("Effect upload failed.");

        /* Play it the indicated number of times */
        writeJoystick(count);
    }
}
#else
void EvdevJoystick::ffDirectionalConstant(int, float, float, float, float, float)
{
}
#endif

#ifdef HAVE_FF_EFFECT_DIRECTIONAL
void EvdevJoystick::ffDirectionalPeriodic(int count, float delay, float duration,
        float x_direction, float y_direction,
        float amplitude, float period,
        PeriodicType type)
{
    if (!ffHasDirectional())
        return;

    /* whenever we switch effect types we must reset the effect
     * this could be avoided by tracking the slot numbers and only
     * using one for each type.
     * When we don't switch types we still must stop the previous
     * effect we were playing, if any.
     */
    if (ff_rumble->type != FF_PERIODIC)
        ffResetEffect();
    else if (ff_rumble->id != -1)
        writeJoystick(0);

    if (count > 0)
    {
        /* Download an updated effect */
        ff_rumble->type = FF_PERIODIC;
        int wave = 0;
        switch (type)
        {
        case FF_Sine:
            wave = FF_SINE;
            break;
        case FF_Square:
            wave = FF_SQUARE;
            break;
        case FF_Triangle:
            wave = FF_TRIANGLE;
            break;
        case FF_SawtoothUp:
            wave = FF_SAW_UP;
            break;
        case FF_SawtoothDown:
            wave = FF_SAW_DOWN;
            break;
        default:
            printError("Unknown periodic force feedback waveform.");
            return;
        }
        ff_rumble->u.periodic.waveform = wave;
        ff_rumble->u.periodic.magnitude = (int) (0x7FFF * amplitude + 0.5f);
        ff_rumble->u.periodic.period = (int) (period * 1000 + 0.5f);
        ff_rumble->u.periodic.offset = ff_rumble->u.periodic.phase = 0;
        ff_rumble->direction = (int) (0xFFFF * (1.0 / (2.0 * M_PI)) *
                                      atan2(x_direction, -y_direction) + 0.5);
        ff_rumble->u.periodic.envelope.attack_length = FF_NOMINAL_MIN;
        ff_rumble->u.periodic.envelope.fade_length = FF_NOMINAL_MIN;
        ff_rumble->u.periodic.envelope.attack_level = ff_rumble->u.periodic.magnitude;
        ff_rumble->u.periodic.envelope.fade_level = ff_rumble->u.periodic.magnitude;
        ff_rumble->replay.length = (int) (duration * 1000 + 0.5f);
        ff_rumble->replay.delay = (int) (delay * 1000 + 0.5f);
        if (ioctl(joystickfd, EVIOCSFF, ff_rumble) == -1)
            printError("Effect upload failed.");

        /* Play it the indicated number of times */
        writeJoystick(count);
    }
}
#else
void EvdevJoystick::ffDirectionalPeriodic(int, float, float, float, float, float,
        float, PeriodicType)
{
}
#endif

#ifdef HAVE_FF_EFFECT_DIRECTIONAL
void EvdevJoystick::ffDirectionalResistance(float time, float coefficient,
        float saturation, ResistanceType type)
{
    if (!ffHasDirectional())
        return;

    /* whenever we switch effect types we must reset the effect
     * this could be avoided by tracking the slot numbers and only
     * using one for each type.
     * When we don't switch types we still must stop the previous
     * effect we were playing, if any.
     */
    if ((ff_rumble->type != FF_SPRING) &&
            (ff_rumble->type != FF_FRICTION) &&
            (ff_rumble->type != FF_DAMPER))
        ffResetEffect();
    else if (ff_rumble->id != -1)
        writeJoystick(0);

    if (1 > 0)
    {
        /* Download an updated effect */
        int lintype;
        switch (type)
        {
        case FF_Position:
            lintype = FF_SPRING;
            break;
        case FF_Velocity:
            lintype = FF_FRICTION;
            break;
        case FF_Acceleration:
            lintype = FF_DAMPER;
            break;
        default:
            printError("Unrecognized force feedback resistance type.");
            return;
        }
        ff_rumble->type = lintype;
        ff_rumble->u.condition[0].right_saturation =
            ff_rumble->u.condition[0].left_saturation = (int) (0x7FFF * saturation + 0.5f);
        ff_rumble->u.condition[0].right_coeff =
            ff_rumble->u.condition[0].left_coeff = (int) (0x7FFF * coefficient + 0.5f);
        ff_rumble->u.condition[0].deadband = 0;
        ff_rumble->u.condition[0].center = 0;
        ff_rumble->u.condition[1] = ff_rumble->u.condition[0];
        ff_rumble->replay.length = (int) (time * 1000 + 0.5f);
        ff_rumble->replay.delay = 0;
        if (ioctl(joystickfd, EVIOCSFF, ff_rumble) == -1)
            printError("Effect upload failed.");

        /* Play it just once */
        writeJoystick(1);
    }
}
#else
void EvdevJoystick::ffDirectionalResistance(float, float, float, ResistanceType)
{
}
#endif

#endif /* HAVE_LINUX_INPUT_H */

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
